/*
 * Copyright (c) 2012, marco.tamburelli@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.bmi.gwt.mi.server;

import java.util.List;
import java.util.regex.Pattern;

import javassist.NotFoundException;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.apache.log4j.Logger;
import org.bmi.gwt.mi.server.annotation.ClassMiHandler;
import org.bmi.gwt.mi.server.annotation.ObjectMiHandler;
import org.bmi.gwt.mi.server.annotation.QueueHandler;
import org.bmi.gwt.mi.server.channel.ChannelFactory;
import org.bmi.gwt.mi.server.core.event.HandlersManager;
import org.bmi.gwt.mi.server.core.queue.QueuesManager;
import org.bmi.gwt.mi.server.events.MiEventHandler;
import org.bmi.gwt.mi.server.events.QueueEventHandler;
import org.bmi.gwt.mi.server.events.StartupEvent;
import org.bmi.gwt.mi.server.events.StartupEventHandler;
import org.bmi.gwt.mi.server.util.Discovery;
import org.bmi.gwt.mi.shared.BroadChannel;
import org.bmi.gwt.mi.shared.Channel;
import org.bmi.gwt.mi.shared.MiObject;
import org.bmi.gwt.mi.shared.exceptions.NameParseException;
import org.bmi.gwt.mi.shared.exceptions.NotAllowedQueueException;

@WebListener
public class MiStartup implements ServletContextListener
{
	static protected Logger logger = Logger.getLogger(MiStartup.class);

	private QueuesManager queuesManager;
	private HandlersManager handlersManager;
	private ChannelFactory channelFactory;

	@Override
	public void contextInitialized(ServletContextEvent sce)
	{
		try
		{
			ServletContext context = sce.getServletContext();

			this.queuesManager = new QueuesManager();
			this.handlersManager = new HandlersManager(context);
			this.channelFactory = new ChannelFactory();

			this.queuesManager.setHandlersManager(this.handlersManager);
			this.handlersManager.setChannelfactory(this.channelFactory);
			this.channelFactory.setQueuesManager(this.queuesManager);

			this.queuesManager.startQueueScheduler();

			context.setAttribute(QueuesManager.class.getName(), this.queuesManager);
			context.setAttribute(HandlersManager.class.getName(), this.handlersManager);
			context.setAttribute(ChannelFactory.class.getName(), this.channelFactory);

			Discovery discovery = new Discovery();

			for (String resourceName : context.getResourcePaths("/WEB-INF/lib"))
				/* Excluding unnecessary libraries */
				if (!resourceName.equals("/WEB-INF/lib/javassist.jar")
						&& !resourceName.equals("/WEB-INF/lib/gwt-servlet.jar"))
					discovery.addScanner(discovery.createJarScanner(context.getRealPath(resourceName)));

			discovery.addScanner(discovery.createDeepFileScanner(context.getRealPath("/WEB-INF/classes")));

			try
			{
				loadQueueHandlers(discovery);
				loadMiHandlers(discovery);

				/*
				 * Startup handlers should be fired after others, because
				 * provided queue could require an event handler which could not
				 * be yet loaded.
				 */
				loadBoxHandlers(discovery);
			}
			catch (Throwable e)
			{
				logger.error("[MiStartup] - Error while scanning classes: ", e);
			}
		}
		catch (NotFoundException e)
		{
			logger.error("[MiStartup] - Error while initializing: ", e);
		}
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce)
	{
		this.queuesManager.stopQueueScheduler();
	}

	private void loadBoxHandlers(Discovery discovery) throws InstantiationException, IllegalAccessException
	{
		List<Class<? extends StartupEventHandler>> types = discovery.getSubTypesOf(StartupEventHandler.class);

		for (Class<? extends StartupEventHandler> clazz : types)
			clazz.newInstance().onInitialize(new StartupEvent()
			{
				@Override
				public boolean isQueueAlive(String queueName) throws NameParseException
				{
					if (!Pattern.matches("((\\w+)\\.)*(\\w+)", queueName))
						throw new NameParseException("Invalid queue name: " + queueName);

					return MiStartup.this.queuesManager.isQueueAlive(queueName);
				}

				@Override
				public BroadChannel getBroadChannel(String queueExpression) throws NameParseException
				{
					if (!Pattern.matches("((\\w+)\\.)*(\\*)", queueExpression))
						throw new NameParseException("Invalid queue expression: " + queueExpression);

					return MiStartup.this.channelFactory.getBroadChannel(queueExpression);
				}

				@Override
				public Channel getChannel(String queueName) throws NameParseException, NotAllowedQueueException
				{
					if (!Pattern.matches("((\\w+)\\.)*(\\w+)", queueName))
						throw new NameParseException("Invalid queue name: " + queueName);

					return MiStartup.this.channelFactory.getChannel(queueName);
				}
			});
	}

	private void loadQueueHandlers(Discovery discovery) throws InstantiationException, IllegalAccessException,
			NameParseException
	{
		List<Class<? extends QueueEventHandler>> types = discovery.getSubTypesOf(QueueEventHandler.class);

		for (Class<? extends QueueEventHandler> clazz : types)
		{
			QueueHandler anno = clazz.getAnnotation(QueueHandler.class);

			String queueName = anno != null ? anno.queueExpression() : "*";
			logger.info("[MiStartup.loadQueueHandlers] - adding event handler with queue expression: " + queueName);

			this.handlersManager.addQueueEventHandler(queueName, clazz.newInstance());
		}
	}

	private void loadMiHandlers(Discovery discovery) throws InstantiationException, IllegalAccessException
	{
		List<Class<? extends MiEventHandler>> types = discovery.getSubTypesOf(MiEventHandler.class);

		for (Class<? extends MiEventHandler> clazz : types)
		{
			if (clazz.isAnnotationPresent(ObjectMiHandler.class))
			{
				ObjectMiHandler anno = clazz.getAnnotation(ObjectMiHandler.class);

				String objId = anno.objectId();
				logger.info("[MiStartup.loadMiHandlers] - adding event handler for object id: " + objId);

				this.handlersManager.addObjMiHandler(objId, clazz.newInstance());
			}
			if (clazz.isAnnotationPresent(ClassMiHandler.class))
			{
				ClassMiHandler anno = clazz.getAnnotation(ClassMiHandler.class);

				Class<? extends MiObject> objClass = anno.objectClass();
				logger.info("[MiStartup.loadMiHandlers] - adding event handler for object class: " + objClass);

				this.handlersManager.addClassMiHandler(objClass.getName(), clazz.newInstance());
			}
		}
	}
}
